﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Crm.Sdk;
using System.Reflection;
using Streamsol.Helpers;

namespace Streamsol.Crm.XrmDevelopmentToolkit
{
    public static class DynamicEntityHelper
    {
        /// <summary>
        /// Converts a BusinessEntity into a DynamicEntity
        /// </summary>
        /// <param name="entity">The entity to convert to a DynamicEntity</param>
        /// <returns>A populated DynamicEntity</returns>
        public static DynamicEntity AsDynamic(this BusinessEntity entity)
        {
            Type entType = entity.GetType();
            if (entType.Name.Equals("DynamicEntity"))
                return (DynamicEntity)entity;

            DynamicEntity dyn = new DynamicEntity();
            dyn.Name = entType.Name;
            PropertyInfo[] pInfo = entType.GetProperties();
            foreach (PropertyInfo p in pInfo)
            {
                object v = p.GetValue(entity, null);
                if (v != null)
                {
                    Property prop = CreateProperty(p.PropertyType, p.Name, v);
                    if (prop != null)
                        dyn.Properties.Add(prop);
                }
            }
            return dyn;
        }

        /// <summary>
        /// Convert a dynamic entity into a strongly typed business entity.
        /// </summary>
        /// <typeparam name="T">The entity type to convert to</typeparam>
        /// <param name="entity">The dynamic entity</param>
        /// <returns>A new strongly typed business entity</returns>
        public static T AsEntity<T>(this DynamicEntity entity) where T : BusinessEntity, new()
        {
            T ent = new T();
            foreach (Property p in entity.Properties)
            {
                PropertyInfo prop = typeof(T).GetProperty(p.Name);
                if (prop != null)
                    prop.SetValue(ent, entity.GetPropertyValue(p.Name), null);
            }
            return ent;
        }

        /// <summary>
        /// This method returns the value of a dynamic entity attribute.
        /// </summary>
        /// <param name="entity">The entity</param>
        /// <param name="attribute">The attribute name</param>
        /// <returns>The value of the specified entity attribute. Returns null if no matching attribute is found.</returns>
        public static object GetCrmProperty(this DynamicEntity entity, string attribute)
        {
       
            return entity.GetCrmProperty<object>(attribute);
        }

        /// <summary>
        /// Returns the CRM property from a given dynamic entity.
        /// </summary>
        /// <typeparam name="TProperty">The type of property to retrieve.</typeparam>
        /// <param name="entity">The dynmaic entity</param>
        /// <param name="attribute">The name of the property</param>
        /// <returns>A <typeparamref name="TProperty"/>.</returns>
        public static TProperty GetCrmProperty<TProperty>(this DynamicEntity entity, string attribute)
        {
            foreach (Property prop in entity.Properties)
            {
                if (prop.Name == attribute)
                {
                    PropertyInfo field = prop.GetType().GetProperty("Value");
                    if (prop.GetType().Namespace.Equals("Microsoft.Crm.Sdk"))
                        return (TProperty)field.GetValue(prop, null);                    
                }
            }
            return default(TProperty);
        }

        /// <summary>
        /// This method returns the value of a dynamic entity attribute.
        /// </summary>
        /// <param name="entity">The entity</param>
        /// <param name="attribute">The attribute name</param>
        /// <returns>The value of the specified entity attribute. Returns null if no matching attribute is found.</returns>
        public static object GetPropertyValue(this DynamicEntity entity, string attribute)
        {
            return entity.GetPropertyValue<object>(attribute);
        }

        /// <summary>
        /// This method returns the value of a dynamic entity attribute.
        /// </summary>
        /// <param name="entity">The entity</param>
        /// <param name="attribute">The attribute name</param>
        /// <returns>The value of the specified entity attribute. Returns null if no matching attribute is found.</returns>
        public static TAttributeType GetPropertyValue<TAttributeType>(this DynamicEntity entity, string attribute)
        {
            foreach (Property prop in entity.Properties)
            {
                if (prop.Name == attribute)
                {
                    PropertyInfo field = prop.GetType().GetProperty("Value");
                    if (typeof(TAttributeType).Namespace.Equals("Microsoft.Crm.Sdk"))
                        return (TAttributeType)field.GetValue(prop, null);
                    else
                    {
                        object propValue = field.GetValue(prop, null);
                        if (propValue.GetType().FullName.StartsWith("System"))
                            return (TAttributeType)propValue;
                        PropertyInfo value = propValue.GetType().GetProperty("Value");
                        return (TAttributeType)value.GetValue(propValue, null);
                    }
                }
            }
            return default(TAttributeType);
        }

        /// <summary>
        /// Sets a specified value to the dynamic entity
        /// </summary>
        /// <typeparam name="TAttributeType">The type of attribute, i.e. CrmBoolean</typeparam>
        /// <param name="entity"></param>
        /// <param name="attributeName">The name of the attribute</param>
        /// <param name="value">The value of the attribute, this will usually be a CrmType i.e CrmBoolean</param>
        public static void SetProperty(this DynamicEntity entity, string attributeName, object value)
        {
            entity.Properties.Add(CreateProperty(value.GetType(), attributeName, value));
        }

        /// <summary>
        /// A method to create a CrmProperty
        /// </summary>
        /// <typeparam name="TCrmProperty">e.g. CrmBooleanProperty</typeparam>
        /// <typeparam name="TAttribute">e.g. CrmBoolean</typeparam>
        /// <typeparam name="TValue">e.g. bool</typeparam>
        /// <param name="attributeName">The name of the attribute</param>
        /// <param name="value">The value to set. e.g. true</param>
        /// <returns></returns>
        internal static TCrmProperty CreateProperty<TCrmProperty, TAttribute, TValue>(string attributeName, TValue value)
            where TCrmProperty : Property, new()
            where TAttribute : new()
        {
            TAttribute attribute = new TAttribute();

            Type attType = typeof(TAttribute);

            //TODO: Checking for the CRM namespace to enforce CRM types.
            //... if(!attType.Namespace.Equals("Micorosoft.Crm.Sdk")) throw new InvalidTypeException();

            PropertyInfo piAttr = attType.GetProperty("Value");
            piAttr.SetValue(attribute, value, null);

            PropertyInfo piAttrName = attType.GetProperty("name");
            piAttrName.SetValue(attribute, attributeName, null);

            TCrmProperty property = new TCrmProperty() { Name = attributeName };

            Type propType = typeof(TCrmProperty);
            PropertyInfo piProp = propType.GetProperty("Value");
            piProp.SetValue(property, attribute, null);

            return property;
        }

        internal static Property CreateProperty<TAttributeType>(string attributeName, TAttributeType value)
        {
            return CreateProperty(typeof(TAttributeType), attributeName, value);
        }

        /// <summary>
        /// Creates a Property for DynamicEntitied
        /// </summary>
        /// <param name="attType">The crm type of the attribute</param>
        /// <param name="attName">The name of the attribute</param>
        /// <param name="value">The value of the attribute</param>
        /// <returns>A Property</returns>
        public static Property CreateProperty(Type crmType, string attName, object value)
        {
            if (crmType == typeof(CrmBoolean) || crmType == typeof(bool))
                return new CrmBooleanProperty()
                {
                    Name = attName,
                    Value = (CrmBoolean)value
                };
            else if (crmType == typeof(CrmDateTime))
            {
                return new CrmDateTimeProperty()
                {
                    Name = attName,
                    Value = (CrmDateTime)value
                };
            }
            else if (crmType == typeof(CrmDecimal))
            {
                return new CrmDecimalProperty()
                {
                    Name = attName,
                    Value = (CrmDecimal)value
                };
            }
            else if (crmType == typeof(CrmFloat))
            {
                return new CrmFloatProperty()
                {
                    Name = attName,
                    Value = (CrmFloat)value
                };
            }
            else if (crmType == typeof(CrmMoney))
            {
                return new CrmMoneyProperty()
                {
                    Name = attName,
                    Value = (CrmMoney)value
                };
            }
            else if (crmType == typeof(CrmNumber))
            {
                return new CrmNumberProperty()
                {
                    Name = attName,
                    Value = (CrmNumber)value
                };
            }
            else if (crmType == typeof(CrmReference))
            {
                throw new NotImplementedException("Cannot convert a CrmReference type to the appropriate Property type");
            }
            else if (crmType == typeof(Customer))
            {
                return new CustomerProperty()
                {
                    Name = attName,
                    Value = (Customer)value
                };
            }
            else if (crmType == typeof(Key))
            {
                return new KeyProperty()
                {
                    Name = attName,
                    Value = (Key)value
                };

            }
            else if (crmType == typeof(Lookup))
            {
                return new LookupProperty()
                {
                    Name = attName,
                    Value = (Lookup)value
                };
            }
            else if (crmType == typeof(Owner))
            {
                return new OwnerProperty()
                {
                    Name = attName,
                    Value = (Owner)value
                };
            }
            else if (crmType == typeof(Picklist))
            {
                return new PicklistProperty()
                {
                    Name = attName,
                    Value = (Picklist)value
                };
            }
            else if (crmType == typeof(Status))
            {
                return new StatusProperty()
                {
                    Name = attName,
                    Value = (Status)value
                };
            }
            else if (crmType == typeof(String))
            {
                return new StringProperty()
                {
                    Name = attName,
                    Value = (string)value
                };
            }
            else if (crmType.Name.EndsWith("StateInfo", StringComparison.CurrentCulture))
            {
                return new StateProperty(attName, value.ExtractPropertyValue<string>("formattedvalue"));
            }
            return null;
        }
    }
}
